/**
 * @class 音频管理器
 * @author DuskyHuang 圣子
 * @description 提供音效、背景音乐的播放和资源管理等功能（可在Root面板上设置）。
*/

import { AudioClip, AudioSource } from 'cc';
import { __cfw_notifications__ } from '../configs';
import { inner } from '../core/boot';
import { singleton } from '../utils/decorators';

@singleton
export default class AudioManager {

    declare public static readonly instance: AudioManager;

    /** 背景音乐资源 */
    public get bgm(): AudioClip { return this._bgmAds?.clip ?? this._bgmClip; }
    public set bgm(arg: AudioClip) {
        this._bgmClip = arg;
        if (this._bgmAds) {
            this._bgmAds.clip = arg;
            this._tryAutoPlay();
        }
    }

    /** 背景音乐音量 */
    public get bgmVolume(): num.pct { return this._bgmAds?.volume; }
    public set bgmVolume(arg: num.pct) { if (this._bgmAds) this._bgmAds.volume = arg; }

    /** 背景音乐循环播放 */
    public get bgmLoop(): boolean { return this._bgmAds?.loop; }
    public set bgmLoop(arg: boolean) { if (this._bgmAds) this._bgmAds.loop = arg; }

    /** 背景音乐自动播放 [TODO.APPLE强制播放] */
    public get bgmPlayOnAwake(): boolean { return this._bgmAutoPlay; }
    public set bgmPlayOnAwake(arg: boolean) { this._bgmAutoPlay = arg; this._tryAutoPlay(); }

    /** 音效资源组 */
    public sfx: AudioClip[] = [];

    /** 
     * 按钮默认资源
     * @explain 设置该资源会使得所有按钮启用相同音频。
     * 在层级面板中对指定按钮节点设置名称：`节点:xxx音效`可指定某按钮播放特殊音效。
     * 在层级面板中对指定按钮节点设置名称：`节点:none`可禁用特定节点音效。
     */
    public sfxButton: AudioClip = null;

    /** 
     * 切换按钮默认资源
     * @explain 当切换按钮有次资源时优先播放sfxToggle，否则若有sfxButton则播放后者。
     */
    public sfxToggle: AudioClip = null;

    /** 音效音量 */
    public get sfxVolume(): num.pct { return this._sfxAds?.volume; }
    public set sfxVolume(arg: num.pct) { if (this._sfxAds) this._sfxAds.volume = arg; }

    /** 播放背景音乐 */
    public playBgm(): void {
        this._checkAds(ads => !ads.playing && ads.play());
    }

    /** 暂停背景音乐 */
    public pauseBgm(): void {
        this._checkAds(ads => ads.playing && ads.pause());
    }

    /** 切换背景音乐播放状态 */
    public toggleBgm(): void {
        this._checkAds(ads => {
            ads.playing ? ads.pause() : ads.play();
        });
    }

    /** 添加音效资源 */
    public addSfx(...clips: AudioClip[]): this { this.sfx.unshiftUnique(...clips); return this; }

    /** 
     * 播放音效资源
     * @param name 音效名称
     * @explain 如果你添加的资源名称为`Fight`、`sfx_fight`，你可以使用playSfx('fight')来播放它。 
     */
    public playSfx(name: string): void {
        if (this._muted) return;
        this._checkSfx(() => {
            const name_low = name.toLowerCase();
            this.sfx.forEach(clip => {
                const clip_low = clip.name.toLowerCase();
                if (clip_low === name_low || clip_low === `sfx_${name_low}`) {
                    this._sfxAds.clip = clip;
                    this._sfxAds.play();
                }
            });
        });
    }

    private _muted: boolean = false;
    private _wasBgmPlaying: boolean = false;
    public get mute(): boolean { return this._muted; }
    public set mute(arg: boolean) {
        this._muted = arg;
        if (arg) {
            if (this._bgmAds) {
                this._wasBgmPlaying = this._bgmAds.playing;
                this._bgmAds.pause();
            }
        } else {
            if (this._bgmAds && this._wasBgmPlaying) {
                this._bgmAds.play();
            }
        }
    }

    public init(bgmAds: AudioSource, sfxAds: AudioSource): void {
        this._bgmAds = bgmAds;
        this._sfxAds = sfxAds;
        // notice:cocos ads default playOnAwake, on Root will auto-play every scene's entry
        this._bgmAds.playOnAwake = this._sfxAds.playOnAwake = false;
        inner.attach(__cfw_notifications__.Audio.DEFAULT_BUTTON, this.onSfxButton, this);
        inner.attach(__cfw_notifications__.Audio.DEFAULT_TOGGLE, this.onSfxToggle, this);
    }

    protected onSfxButton(sfx?: string): void {
        if (this._muted) return;
        if (sfx === 'none') return;
        else if (!!sfx) this.playSfx(sfx);
        else if (this.sfxButton) {
            this._sfxAds.clip = this.sfxButton;
            this._sfxAds.play();
        }
    }

    protected onSfxToggle(sfx?: string): void {
        if (this._muted) return;
        if (sfx === 'none') return;
        else if (!!sfx) this.playSfx(sfx);
        else if (this.sfxToggle) {
            this._sfxAds.clip = this.sfxToggle;
            this._sfxAds.play();
        } else this.onSfxButton(sfx);
    }

    private _checkAds(next: Handler<[AudioSource?]>): void {
        const { _bgmAds: ads, bgm: clip } = this;
        if (!ads) return console.warn("[audio]", "no bgm audio-source");
        if (!clip) return console.warn("[audio]", "no bgm audio-clip");
        if (isFunc(next)) next(ads);
    }

    private _checkSfx(next: Handler<[AudioSource?]>): void {
        const { _sfxAds: ads, sfx: clips } = this;
        if (!ads) return console.warn("[audio]", "no sfx audio-source");
        if (!notEmptyArr(clips)) return console.warn("[audio]", "no sfx audio-clips");
        if (isFunc(next)) next(ads);
    }

    private _tryAutoPlay(): void {
        if (this._bgmAutoPlay && this._bgmAds?.clip) this._bgmAds.play();
    }

    private _bgmClip: AudioClip = null;
    private _bgmAutoPlay: boolean = false;
    private _bgmAds: AudioSource = null;
    private _sfxAds: AudioSource = null;
}